package com.AdvancedDemos.集合.双列集合;

import java.util.*;

public class LearnHashMap_LinkedHashMap_TreeMap {
    public static void main(String[] args) {
        /*
         * ◉ HashMap(由键决定特点):无序,不重复,无索引 (用的最多)
         *  HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表(JDK8前数组+链表,JDK8之后数组+链表+红黑树)实现的
         * 实际上,Set系列底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已
         *
         * HashMap集合是一种增删改查数据,性能都较好的集合
         * 但它是无序的,不能重复的,没有索引支持的
         *
         * 往HashSet集合中存储元素时，底层调用了元素的两个方法：一个是hashCode方法获取元素的hashCode值（哈希值）；另一个是调用了元素的equals方法，用来比较新添加的元素和集合中已有的元素是否相同。
        - 只有新添加元素的hashCode值和集合中已有元素的hashCode值相同、新添加的元素才调用equals方法和集合中已有元素比较,若结果为true, 才认为元素重复。
        - 如果hashCode值相同，equals比较结果不同，则认为是不同的元素,以链表的形式连接在数组的同一个索引为位置
          在JDK8开始后，为了提高性能，当链表的长度超过8时，就会把链表转换为红黑树

        自定义类型元素去重原理:
        HashMap的键依赖hashCode方法和equals方法保证键唯一,
        默认不同对象的哈希值不同,从而HashMap会认为同类型的对象都是不相同的,
        也就是说如果集合中的元素是自定义类型,那么想要去重需要重写hashCode方法和equals方法
        重写hashCode使得类型相同但是元素内容不同的对象产生相同哈希值,从而同类型对象在底层数组中一定位于同一位置,再通过重写equals来判断自定义类型对内容是否相同,相同就不添加到哈希表中,从而达到自定义类型去重的目的
         */

        Map<Stu, String> map = new HashMap<>();
        map.put(new Stu(25, "蜘蛛精", 168.5), "盘丝洞");
        map.put(new Stu(25, "蜘蛛精", 168.5), "水帘洞");
        map.put(new Stu(23, "至尊宝", 163.5), "水帘洞");
        map.put(new Stu(28, "牛魔王", 183.5), "火焰山");
        System.out.println(map);//{Stu{age=28, name='牛魔王', height=183.5}=火焰山, Stu{age=25, name='蜘蛛精', height=168.5}=水帘洞, Stu{age=23, name='至尊宝', height=163.5}=水帘洞}
        //重写hashCode()与equals()后实现了自定义类型去重
        //留下的是后添加的Stu{age=25, name='蜘蛛精', height=168.5}=水帘洞


        /*
         * ◉ LinkedHashMap(由键决定特点):有序,不重复,无索引
         *
         * LinkedHasMap底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(用以保证有序)
         * 实际上,原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap,只不过LinkedHashSet只要LinkedHashMap中的键元素,不要值元素
         *
         */
        System.out.println("----------------");
        LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("手表", 100);
        linkedHashMap.put("手表", 220);
        linkedHashMap.put("手机", 2);
        linkedHashMap.put("Java", 2);
        linkedHashMap.put(null, null);
        System.out.println(linkedHashMap);//{手表=220, 手机=2, Java=2, null=null}



        /*
         * ◉ TreeMap(由键决定特点):按照大小默认升序排序,不重复,无索引
         * TreeMap与TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序,TreeSet只要TreeMap键不要值
         *
         * TreeMap集合同样也支持两种方式来指定排序规则
         * ① 让自定义类实现Comparable接口,重写compareTo函数,来自定义比较规则
         * ② TreeMap集合有一个有参构造函数,支持创建Comparator比较器对象,通过匿名内部类对象的做实参的形式来自定义比较规则
         */

        System.out.println("---------------");

        Map<Stu, String> treeMap = new TreeMap<>((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()));//比较器优先于实现可比较接口
        treeMap.put(new Stu(25, "蜘蛛精", 168.5), "盘丝洞");
        treeMap.put(new Stu(25, "蜘蛛精", 168.5), "水帘洞");
        treeMap.put(new Stu(23, "至尊宝", 165.5), "水帘洞");
        treeMap.put(new Stu(28, "牛魔王", 182.5), "牛头山");
        System.out.println(treeMap);//{Stu{age=23, name='至尊宝', height=165.5}=水帘洞, Stu{age=25, name='蜘蛛精', height=168.5}=水帘洞, Stu{age=28, name='牛魔王', height=182.5}=牛头山}

    }
}


class Stu implements Comparable<Stu> {
    private int age;
    private String name;
    private double height;

    public Stu() {
    }

    public Stu(int age, String name, double height) {
        this.age = age;
        this.name = name;
        this.height = height;
    }

    @Override
    public boolean equals(Object o) {//根据对象内容判读两个对象是否相同
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Stu stu = (Stu) o;
        return age == stu.age && Double.compare(stu.height, height) == 0 && Objects.equals(name, stu.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(age, name, height);//根据属性值产生哈稀值,使得内容相同的对象哈希值相同,在底层哈希表中位于同一列
    }

    @Override
    public String toString() {
        return "Stu{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", height=" + height +
                '}';
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }


    @Override
    public int compareTo(Stu o) {
        return this.age - o.age;
    }
}